#include <sstream>
#include <algorithm>
#include <map>
#include <string.h>
#include <ctype.h>
#include "BBS2chProxyURL.h"
#include "BBS2chProxyFormData.h"

static std::string trim(const std::string& str, const char *trimCharacters = " \t\v\r\n")
{
	std::string trimmed;
	size_t start = str.find_first_not_of(trimCharacters);
	if (start != std::string::npos) {
		size_t end = str.find_last_not_of(trimCharacters);
		trimmed = str.substr(start, end-start+1);
	}
	return trimmed;
}

static std::string trimEnd(const std::string& str, const char *trimCharacters = " \t\v\r\n")
{
	std::string trimmed;
	size_t pos = str.find_last_not_of(trimCharacters);
	if (pos != std::string::npos) {
		trimmed = str.substr(0, pos+1);
	}
	return trimmed;
}

BBS2chProxyURL::BBS2chProxyURL(const char *url) : _port(0), _dirty(true)
{
	const char *ptr = url;
	while (isspace(*ptr)) ptr++;
	const char *start = ptr;
	ptr = strstr(start, "://");
	if (ptr) {
		_scheme = std::string(start, ptr-start);
		ptr += 3;
	}
	if (!ptr) {
		ptr = start;
	}
	std::transform(_scheme.begin(), _scheme.end(), _scheme.begin(), tolower);
	start = ptr;
	const char *ptr2 = NULL;
	while (*ptr != '/' && *ptr) {
		if (*ptr == '@') {
			_auth = std::string(start, ptr-start);
			start = ptr+1;
			ptr2 = NULL;
		}
		else if (*ptr == ':') ptr2 = ptr;
		ptr++;
	}
	if (ptr2) {
		_port = atoi(ptr2+1);
		_host = std::string(start, ptr2-start);
	}
	else {
		_host = std::string(start, ptr-start);
	}
	std::transform(_host.begin(), _host.end(), _host.begin(), tolower);
	_pathAndOthers = trimEnd(std::string(ptr));
	if (_scheme.empty()) {
		if (_port == 443) _scheme = "https";
		else _scheme = "http";
	}
	if (_pathAndOthers.empty()) _pathAndOthers = "/";
	if (_port == 80 && _scheme == "http") _port = 0;
	else if (_port == 443 && _scheme == "https") _port = 0;
}

BBS2chProxyURL::BBS2chProxyURL(const char *scheme, const char *host) : _port(0), _dirty(true)
{
	_scheme = trim(std::string(scheme));
	_host = trim(std::string(host));
	_pathAndOthers = "/";
	std::transform(_scheme.begin(), _scheme.end(), _scheme.begin(), tolower);
	std::transform(_host.begin(), _host.end(), _host.begin(), tolower);
}

BBS2chProxyURL::BBS2chProxyURL(const char *scheme, const char *host, int port, const char *path) : _dirty(true)
{
	_scheme = trim(std::string(scheme));
	_host = trim(std::string(host));
	_port = port;
	if (*path == '/') _pathAndOthers = trim(std::string(path));
	else _pathAndOthers = "/" + trim(std::string(path));
	std::transform(_scheme.begin(), _scheme.end(), _scheme.begin(), tolower);
	std::transform(_host.begin(), _host.end(), _host.begin(), tolower);
	if (_port == 80 && _scheme == "http") _port = 0;
	else if (_port == 443 && _scheme == "https") _port = 0;
}

BBS2chProxyURL::BBS2chProxyURL(const BBS2chProxyURL &baseURL, const char *path) : _dirty(true)
{
	_scheme = baseURL._scheme;
	_host = baseURL._host;
	_auth = baseURL._auth;
	_port = baseURL._port;
	if (*path == '/') _pathAndOthers = trim(std::string(path));
	else _pathAndOthers = "/" + trim(std::string(path));
}

const std::string& BBS2chProxyURL::absoluteString()
{
	if (_dirty) {
		if (_port) {
			std::ostringstream url;
			url << _scheme << "://";
			if (!_auth.empty()) url << _auth << "@";
			url << _host;
			url << ":" << _port;
			url << _pathAndOthers;
			_absoluteURLString = url.str();
		}
		else {
			_absoluteURLString = _scheme;
			_absoluteURLString += "://";
			if (!_auth.empty()) {
				_absoluteURLString += _auth;
				_absoluteURLString += '@';
			}
			_absoluteURLString += _host;
			_absoluteURLString += _pathAndOthers;
		}
		_dirty = false;
	}
	return _absoluteURLString;
}

const std::string& BBS2chProxyURL::getScheme() const
{
	return _scheme;
}

const std::string& BBS2chProxyURL::getHost() const
{
	return _host;
}

const std::string& BBS2chProxyURL::getPathAndOthers() const
{
	return _pathAndOthers;
}

std::string BBS2chProxyURL::getPath() const
{
	size_t queryBegin = _pathAndOthers.find("?");
	size_t fragmentBegin = _pathAndOthers.find("#");
	return _pathAndOthers.substr(0, std::min(queryBegin, fragmentBegin));
}

int BBS2chProxyURL::getPort() const
{
	return _port;
}

void BBS2chProxyURL::setScheme(const std::string &scheme)
{
	_scheme = scheme;
	_port = 0;
	_dirty = true;
}

void BBS2chProxyURL::setPort(int port)
{
	if (port == _port) return;
	_port = port;
	if (_port == 80 && _scheme == "http") _port = 0;
	else if (_port == 443 && _scheme == "https") _port = 0;
	_dirty = true;
}

bool BBS2chProxyURL::isHttp() const
{
	return _scheme == "http" || _scheme == "https";
}

bool BBS2chProxyURL::isValid() const
{
	return !_scheme.empty() && !_host.empty();
}

bool BBS2chProxyURL::equalsTo(const BBS2chProxyURL &url, bool ignoreHttpOrHttps) const
{
	if (ignoreHttpOrHttps && isHttp() && url.isHttp()) {
		return _host == url._host && _pathAndOthers == url._pathAndOthers;
	}
	return _scheme == url._scheme && _host == url._host && _port == url._port && _pathAndOthers == url._pathAndOthers;
}

bool BBS2chProxyURL::isKindOfHost(const std::string &hostSuffix) const
{
	size_t len1 = _host.length();
	size_t len2 = hostSuffix.length();
	if (len1 == len2) return (_host == hostSuffix);
	else if (len1 > len2) {
		return (_host.at(len1-len2-1) == '.') && (_host.compare(len1-len2, len2, hostSuffix) == 0);
	}
	return false;
}

bool BBS2chProxyURL::replaceHost(const std::string &fromHost, const std::string &toHost)
{
	size_t len1 = _host.length();
	size_t len2 = fromHost.length();
	if (len1 == len2) {
		if (_host == fromHost) {
			_host = toHost;
			_dirty = true;
			return true;
		}
	}
	else if (len1 > len2) {
		if ((_host.at(len1-len2-1) == '.') && (_host.compare(len1-len2, len2, fromHost) == 0)) {
			_host.replace(len1-len2, len2, toHost);
			_dirty = true;
			return true;
		}
	}
	return false;
}

bool BBS2chProxyURL::isFamilyOf5chNet() const
{
	return isKindOfHost("5ch.net") || isKindOfHost("2ch.net") || isKindOfHost("bbspink.com");
}

bool BBS2chProxyURL::pathStartsWith(const std::string &prefix) const
{
	size_t len1 = _pathAndOthers.length();
	size_t len2 = prefix.length();
	if (len1 < len2) return false;
	return _pathAndOthers.compare(0, len2, prefix) == 0;
}

bool BBS2chProxyURL::pathEndsWith(const std::string &suffix) const
{
	size_t len1 = _pathAndOthers.length();
	size_t len2 = suffix.length();
	if (len1 < len2) return false;
	return _pathAndOthers.compare(len1-len2, len2, suffix) == 0;
}

bool BBS2chProxyURL::hostStartsWith(const std::string &prefix) const
{
	size_t len1 = _host.length();
	size_t len2 = prefix.length();
	if (len1 < len2) return false;
	return _host.compare(0, len2, prefix) == 0;
}

std::map<std::string, std::string> BBS2chProxyURL::getQuery() const
{
	std::map<std::string, std::string> fields;
	size_t pos = _pathAndOthers.find("?");
	if (pos != std::string::npos) {
		const char *start = _pathAndOthers.c_str() + pos + 1;
		const char *ptr = start;
		while (*ptr != 0 && *ptr != '#') ptr++;
		BBS2chProxyFormData query(start, ptr-start);
		for (BBS2chProxyFormData::iterator it = query.begin(); it != query.end(); ++it) {
			fields.insert(std::make_pair(it->first, it->second.get()));
		}
	}
	return fields;
}

int BBS2chProxyURL::numberOfPathComponents() const
{
	size_t idx = 0;
	int ret = 0;
	while (idx < _pathAndOthers.size()) {
		size_t pos = _pathAndOthers.find("/", idx);
		if (pos == std::string::npos) {
			ret++;
			break;
		}
		if (pos != idx) ret++;
		idx = pos + 1;
	}
	return ret;
}
